import game_object,threading,copy,pdb,sensor_nclosest_radar
pi=3.1415926535897931
import math
class ga_agent(game_object.game_object):
	"""A game object is one that is in the physical game, has to move, and generate collision events when they occur."""
	def __init__(self,factory,body_time,brain_index,brain,state_in_hidden_out,actions,senses,object_type,object_num,max_velocity,max_heading_diff,radius,graphics_draw,graphics_die,pos,heading,im_file):
		game_object.game_object.__init__(self,actions,senses,object_type,object_num,max_velocity,max_heading_diff,radius,graphics_draw,graphics_die,pos,heading,im_file)
		# Tunable constants
		use_tanh=False

		self.fact=factory
		self.body_time=body_time
		self.brain_index=brain_index                
		self.brain=brain
		self.state_in_hidden_out=state_in_hidden_out
		self.body_lock=threading.Lock()

		self.sig=0
		self.dsig=0
		if use_tanh:
			self.sig=math.tanh
			self.dsig=self.dtanh
		else:
			self.sig=self.sigmoid
			self.dsig=self.dsigmoid

		# Layer 0 is the input layer (you copy the inputs there)
		self.output=[None]*(len(state_in_hidden_out))
		for state in range(len(state_in_hidden_out)): # States
			self.output[state]=[0.0]*len(state_in_hidden_out[state])
			for to_layer in range(len(state_in_hidden_out[state])): # To Layer
				self.output[state][to_layer]=[0.0]*state_in_hidden_out[state][to_layer]
	@staticmethod
	def dtanh(x):
		return 1-x*x
	@staticmethod
	def sigmoid(x):
		return 1.0/(1.0+math.exp(-x))
	@staticmethod
	def dsigmoid(x):
		return x*(1-x)	
	def _compute_output(self,state,input):
		assert(len(input)==len(self.brain[state][0][0])-1)  # Ensure that the input fits our dimensionality
		self.output[state][0]=copy.deepcopy(input)
		for layer in range(len(self.brain[state])):
			for dest in range(len(self.brain[state][layer])):
				valid_from_nodes=len(self.brain[state][layer][dest])-1
				temp_tot=sum(map(lambda x,y: x*y,self.output[state][layer],self.brain[state][layer][dest][0:valid_from_nodes]))-self.brain[state][layer][dest][-1]

				try:
					self.output[state][layer+1][dest]=self.sig(temp_tot)
				except IndexError,err:
					print(err)
					pdb.set_trace()
		return self.output[state][-1]
	@staticmethod
	def norm_ang(angle):
		"""Takes in an angle [-pi,pi] and makes it [0,1]"""
		return (angle+pi)/(2*pi)
	@staticmethod
	def denorm_ang(angle):
		"""Takes in an angle [0,1] and makes it [-pi,pi]"""
		return (angle-.5)*(2*pi)
	def denorm_vel(self,vel):
		"""Takes in a velocity [0,1] and makes it [-max_vel,max_vel]"""
		return (vel-.5)*2*self.max_velocity
	def think(self):
		"""Called to allow the agent to sense/think/act"""
		self.fact.refresh_agent(self) # NOTE This is just to get the agents to switch out faster
		if self.body_lock.acquire(False):
			out=None
		# The states are 0='I have the ball', 1='Someone on my team has the ball', 2='Enemy has the ball', 3=Loose Ball
		# Sense Stage
		state=self.senses['state']()
		goal_distance,goal_direction=self.senses['goal']()
		ball_distance,ball_direction=self.senses['ball']()
		foe=self.senses['enemy_radar']()
		friend=self.senses['friend_radar']()
		ball_range=120
		goal_range=120
		radar_range=120 # Ugly constant, fix this later
		if state==0: # I have the ball
		# Useful sensors:  Direction/distance to the goal 'point', pie slices of teammates, pie slices of enemies
			out=self._compute_output(0,[self.norm_ang(goal_direction),self.norm_ang(foe[0][0]),foe[0][1]/radar_range,self.norm_ang(friend[0][0])]) #[0,1]
		elif state==1: # Someone on my team has the ball
			out=self._compute_output(1,[self.norm_ang(goal_direction),self.norm_ang(foe[0][0]),self.norm_ang(friend[0][0])]) #[0,1]
		elif state==2: # The enemy has the ball
			out=self._compute_output(2,[self.norm_ang(ball_direction),ball_distance/ball_range]) #[0,1]
		elif state==3: # The ball is loose
			out=self._compute_output(3,[self.norm_ang(ball_direction),self.norm_ang(foe[0][0])]) #[0,1]
		else:
			raise StandardError,'Invalid State'
			#pdb.set_trace()
		self.actions['turn_rel'](self.object_num,-self.denorm_ang(out[0])) # [0,1]
		self.actions['move_rel_object'](self.object_num,[0,self.denorm_vel(out[1])]) # [0,1]
		if out[2] > .8: # NOTE Nasty constant
			self.actions['shoot'](self.object_num)
		self.body_lock.release()

	def draw(self):
		"""Draws the object using the provided callback function.  This can use member variables to control different frames of animation to be drawn.  The draw function must be able to setup the initial representation aswell as update it."""
		self.graphics_draw(self.object_num,self.pos,self.heading,self.graphics_state,self.im_file)
	def die(self):
		"""Remove the graphical representation of the object"""
		self.graphics_die(self.object_num)
